package com.zhongkun.datahome.service.service.impl;

import java.io.IOException;
import java.util.*;
import java.util.stream.Collectors;

import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.ExcelWriter;
import com.alibaba.excel.write.metadata.WriteSheet;
import com.alibaba.excel.write.style.column.LongestMatchColumnWidthStyleStrategy;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.zhongkun.datahome.common.exception.ServiceException;
import com.zhongkun.datahome.core.repository.SysDictDataRepository;
import com.zhongkun.datahome.framework.manager.AsyncManager;
import com.zhongkun.datahome.service.constant.FileTypeEnum;
import com.zhongkun.datahome.service.constant.MaterialChannelEnum;
import com.zhongkun.datahome.service.constant.MaterialTypeEnum;
import com.zhongkun.datahome.service.constant.SysDictTypeEnum;
import com.zhongkun.datahome.service.domain.CustomerBase;
import com.zhongkun.datahome.service.domain.CustomerHouse;
import com.zhongkun.datahome.service.domain.HouseBuildPlan;
import com.zhongkun.datahome.service.domain.HouseMaterial;
import com.zhongkun.datahome.service.export.handler.AddNoHandler;
import com.zhongkun.datahome.service.export.handler.CustomCellStyleStrategy;
import com.zhongkun.datahome.service.export.handler.DynamicFreezeHandler;
import com.zhongkun.datahome.service.export.handler.MultiImageCellWriteHandler;
import com.zhongkun.datahome.service.manager.ServiceAsyncFactory;
import com.zhongkun.datahome.service.mapper.MiniProgramNodeSettingMapper;
import com.zhongkun.datahome.service.processor.HouseMaterialDataProcessor;
import com.zhongkun.datahome.service.processor.dto.HouseMaterialDto;
import com.zhongkun.datahome.service.repository.CommonFileRepository;
import com.zhongkun.datahome.service.repository.CustomerHouseRepository;
import com.zhongkun.datahome.service.repository.HouseBuildPlanRepository;
import com.zhongkun.datahome.service.repository.HouseMaterialRepository;
import com.zhongkun.datahome.service.req.cmd.customer.CustomerBaseCmd;
import com.zhongkun.datahome.service.req.cmd.material.HouseMaterialCmd;
import com.zhongkun.datahome.service.req.cmd.sku.SkuCmd;
import com.zhongkun.datahome.service.req.cmd.system.MiniProgramNodeSettingCmd;
import com.zhongkun.datahome.service.req.qry.house.CustomerHouseQry;
import com.zhongkun.datahome.service.req.qry.material.HouseMaterialQry;
import com.zhongkun.datahome.service.req.qry.sku.SkuExistQry;
import com.zhongkun.datahome.service.req.qry.sku.SkuQry;
import com.zhongkun.datahome.service.res.house.CustomerHouseVo;
import com.zhongkun.datahome.service.res.material.HouseMaterialRoomVo;
import com.zhongkun.datahome.service.res.material.HouseMaterialSkuVo;
import com.zhongkun.datahome.service.res.sku.SkuVo;
import com.zhongkun.datahome.service.service.ICustomerBaseService;
import com.zhongkun.datahome.service.service.ICustomerHouseService;
import com.zhongkun.datahome.service.service.IHouseMaterialService;
import com.zhongkun.datahome.service.service.ISkuService;
import com.zhongkun.datahome.service.utils.ExcelUtil;
import com.zhongkun.datahome.service.utils.StreamUtil;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletResponse;

/**
 * 房产材料清单Service业务层处理
 *
 * @author ruoyi
 * @date 2025-04-14
 */
@Service
public class HouseMaterialServiceImpl implements IHouseMaterialService {
    private final Logger log = LoggerFactory.getLogger(this.getClass());

    @Resource
    private HouseMaterialRepository houseMaterialRepository;

    @Resource
    private ISkuService skuService;

    @Resource
    private CommonFileRepository commonFileRepository;

    @Resource
    private SysDictDataRepository sysDictDataRepository;

    @Resource
    private ICustomerBaseService customerBaseService;

    @Resource
    private CustomerHouseRepository customerHouseRepository;

    @Resource
    private MiniProgramNodeSettingMapper miniProgramNodeSettingMapper;

    @Resource
    private HouseBuildPlanRepository houseBuildPlanRepository;

    @Override
    public List<String> listRoom(Long houseId) {
        return houseMaterialRepository.listRoom(houseId);
    }

    @Override
    public List<HouseMaterialRoomVo> listBy(HouseMaterialQry qry) {
        List<HouseMaterial> houseMaterials = houseMaterialRepository.listBy(qry);

        List<HouseMaterial> skuItems = houseMaterials.stream()
                .filter(item -> MaterialTypeEnum.COMMON_SKU.is(item.getMaterialType()))
                .collect(Collectors.toList());

        if (CollectionUtils.isEmpty(skuItems)) {
            return Collections.emptyList();
        }

        List<Long> skuIds = skuItems.stream().map(HouseMaterial::getMaterialId).collect(Collectors.toList());

        SkuQry skuQry = new SkuQry();
        skuQry.setSkuIds(skuIds);
        skuQry.setPageSize(skuIds.size());
        Map<Long, SkuVo> skuMap = StreamUtil.listToMap(skuService.pageSku(skuQry).getList(), SkuVo::getId, e -> e);

        Map<String, List<HouseMaterialSkuVo>> roomMaterialSkuListMap = skuItems.stream()
                .map(item -> HouseMaterialSkuVo.of(item, skuMap.get(item.getMaterialId())))
                .collect(Collectors.groupingBy(HouseMaterialSkuVo::getRoom));

        List<HouseMaterialRoomVo> result = new ArrayList<>();
        roomMaterialSkuListMap.forEach((room, itemList) -> result.add(HouseMaterialRoomVo.of(room, itemList)));

        // 空间排序
        sysDictDataRepository.sortByLabel(SysDictTypeEnum.HOUSE_MATERIAL_ROOM.getType(), result, HouseMaterialRoomVo::getRoom);
        return result;
    }

    @Override
    @Transactional
    public void batchSave(HouseMaterialCmd cmd) {
        CustomerHouse customerHouse = customerHouseRepository.getById(cmd.getHouseId());
        if (Objects.isNull(customerHouse)) {
            throw new ServiceException("房产不存在");
        }

        List<HouseMaterial> oldMaterialList = houseMaterialRepository.listByHouseId(cmd.getHouseId());
        if (CollectionUtils.isEmpty(cmd.getItemList())) {
            if (CollectionUtils.isNotEmpty(oldMaterialList)) {
                List<Long> toRemoveIds = oldMaterialList.stream().map(HouseMaterial::getId).collect(Collectors.toList());
                houseMaterialRepository.removeBatchByIds(toRemoveIds);
            }
            return;
        }

        Map<Boolean, List<HouseMaterialCmd.HouseMaterialItemCmd>> booleanAddMap = cmd.getItemList().stream()
                .collect(Collectors.partitioningBy(item -> Objects.isNull(item.getId())));
        Map<Long, HouseMaterialCmd.HouseMaterialItemCmd> toUpdateCmdMap = StreamUtil.listToMap(booleanAddMap.get(false), HouseMaterialCmd.HouseMaterialItemCmd::getId, e -> e);

        List<Long> toRemoveIds = new ArrayList<>();
        List<HouseMaterial> toUpdateMaterialList = new ArrayList<>();
        oldMaterialList.forEach(oldMaterial -> {
            if (toUpdateCmdMap.containsKey(oldMaterial.getId())) {
                oldMaterial.update(toUpdateCmdMap.get(oldMaterial.getId()));
                toUpdateMaterialList.add(oldMaterial);
                return;
            }
            toRemoveIds.add(oldMaterial.getId());
        });

        if (CollectionUtils.isNotEmpty(toUpdateMaterialList)) {
            houseMaterialRepository.updateBatchById(toUpdateMaterialList);
        }

        if (CollectionUtils.isNotEmpty(toRemoveIds)) {
            houseMaterialRepository.removeBatchByIds(toRemoveIds);
        }

        if (CollectionUtils.isNotEmpty(booleanAddMap.get(true))) {
            List<HouseMaterial> toAddMaterialList = booleanAddMap.get(true).stream()
                    .map(item -> HouseMaterial.of(cmd.getHouseId(), item))
                    .collect(Collectors.toList());
            houseMaterialRepository.saveBatch(toAddMaterialList);
        }
    }

    @Override
    @Transactional
    public List<Long> upload(MultipartFile file) {
        List<Long> createdHouseIds = new ArrayList<>();
        try {
            // 解析行数据
            HouseMaterialDataProcessor processor = new HouseMaterialDataProcessor();
            EasyExcel.read(file.getInputStream(), HouseMaterialDto.class, processor).sheet().doRead();
            List<HouseMaterialDto> dataList = processor.getCachedDataList();

            // 校验房产
            importRowCheck(dataList);

            // 解析并上传图片
            Map<Integer, List<String>> imageUrlsMap = ExcelUtil.uploadImageData(file);

            Map<String, List<String>> skuPicUrlsMap = dataList.stream()
                    .peek(item -> item.setUrls(imageUrlsMap.getOrDefault(item.getRowIndex(), Collections.emptyList())))
                    .collect(Collectors.toMap(HouseMaterialDto::getSkuKey, HouseMaterialDto::getUrls, (v1, v2) -> CollectionUtils.isNotEmpty(v1) ? v1 : v2));

            // 存在的商品清单
            Map<String, Long> existSkuMap = skuService.querySkuExist(dataList.stream()
                    .map(SkuExistQry::of)
                    .collect(Collectors.toList()));

            // 待保存的材料清单
            List<HouseMaterial> toSaveMaterialList = new ArrayList<>();

            // 待删除的清单对应的houseIds
            List<Long> toRemoveListHouseIds = new ArrayList<>();

            // 配置的默认节点
            List<MiniProgramNodeSettingCmd> miniProgramNodeSettings = miniProgramNodeSettingMapper.queryNodeList();
            List<HouseBuildPlan> toSaveHouseBuildPlanList = new ArrayList<>();

            // 按客户分组
            Map<String, List<HouseMaterialDto>> customerMap = StreamUtil.listGroupBy(dataList, HouseMaterialDto::getMobile);
            customerMap.forEach((mobile, customerDataList) -> {
                // 保存客户信息
                CustomerBase customerBase = customerBaseService.save(CustomerBaseCmd.of(customerDataList.get(0)));

                // 查询已存在的房产
                List<CustomerHouse> oldCustomerHouses = customerHouseRepository.listByFamilyId(customerBase.getFamilyId());
                Map<String, CustomerHouse> oldHouseMap = StreamUtil.listToMap(oldCustomerHouses, CustomerHouse::getHouseKey, e -> e);

                // 按房产分组
                Map<String, List<HouseMaterialDto>> customerHouseMap = StreamUtil.listGroupBy(customerDataList, HouseMaterialDto::getHouseKey);
                customerHouseMap.forEach((houseKey, houseDataList) -> {
                    CustomerHouse customerHouse = null;
                    // 保存新房产信息
                    if (!oldHouseMap.containsKey(houseKey)) {
                        customerHouse = CustomerHouse.of(houseDataList.get(0));
                        customerHouse.setSource("back_import");
                        customerHouse.setFamilyId(customerBase.getFamilyId());
                        customerHouseRepository.save(customerHouse);
                        // 后续生成二维码
                        createdHouseIds.add(customerHouse.getId());
                    } else {
                        customerHouse = oldHouseMap.get(houseKey);
                        customerHouse.setHouseName(houseDataList.get(0).getHouseName());
                        customerHouse.setDecoratorStyle(houseDataList.get(0).getDecoratorStyle());
                        customerHouse.setSignStatus(StringUtils.equals(houseDataList.get(0).getSignStatus(), "已签约") ? 1 : 0);
                        customerHouseRepository.updateById(customerHouse);
                        // 后续删除老的清单
                        toRemoveListHouseIds.add(customerHouse.getId());
                    }

                    Long houseId = customerHouse.getId();
                    // 配置的默认节点
                    List<HouseBuildPlan> houseBuildPlans = miniProgramNodeSettings.stream()
                            .map(item -> HouseBuildPlan.of(houseId, item))
                            .collect(Collectors.toList());
                    toSaveHouseBuildPlanList.addAll(houseBuildPlans);

                    // 保存材料清单
                    Set<String> houseSkuKeys = new HashSet<>();
                    houseDataList.forEach(data -> {
                        Long skuId = existSkuMap.get(data.getSkuKey());
                        if (insertSkuFlag(skuId, data, houseSkuKeys)) {
                            //创建sku
                            SkuCmd skuCmd = data.convertSku();
                            skuCmd.setPictureUrls(skuPicUrlsMap.get(data.getSkuKey()));
                            skuCmd.setSource("import_from_house");
                            skuId = skuService.insertSku(skuCmd);

                            // 为新建商品添加绑定关系
                            existSkuMap.put(data.getSkuKey(), skuId);
                            houseSkuKeys.add(data.getSkuKey());
                        }

                        toSaveMaterialList.add(data.convertMaterial(houseId, skuId));
                    });
                });
            });

            if (CollectionUtils.isNotEmpty(toRemoveListHouseIds)) {
                houseMaterialRepository.removeByHouseIds(toRemoveListHouseIds);
            }
            if (CollectionUtils.isNotEmpty(toSaveMaterialList)) {
                houseMaterialRepository.saveBatch(toSaveMaterialList);
            }
            if (CollectionUtils.isNotEmpty(toSaveHouseBuildPlanList)) {
                houseBuildPlanRepository.saveBatch(toSaveHouseBuildPlanList);
            }
            log.info("上传房产清单，新增{}条", toSaveMaterialList.size());
        } catch (IOException e) {
            log.error("上传房产清单，excel文件读取失败", e);
            throw new ServiceException("上传房产清单异常");
        }

        return createdHouseIds;
    }

    private boolean insertSkuFlag(Long skuId, HouseMaterialDto cacheData, Set<String> houseSkuKeys) {
        // 不存在该商品，则需要新增
        if (Objects.isNull(skuId)) {
            return true;
        }

        // 存在该商品，但是对应的品牌/规格为空，且该房产下目前不存在该商品，则需要新增
        return StringUtils.isAnyBlank(cacheData.getBrandName(), cacheData.getSpecification())
                && !houseSkuKeys.contains(cacheData.getSkuKey());
    }

    @Override
    public void export(CustomerHouseQry qry, HttpServletResponse response) {
        ExcelWriter excelWriter = null;
        try {
            // 设置响应头
            ExcelUtil.setExportResponse(response, "导出房产清单");

            // 写出到响应流
            excelWriter = EasyExcel.write(response.getOutputStream(), HouseMaterialDto.class)
                    .registerWriteHandler(new LongestMatchColumnWidthStyleStrategy())    // 自适应列宽
                    .registerWriteHandler(new CustomCellStyleStrategy().getCustomerHouseExcelStrategy())
                    .registerWriteHandler(new AddNoHandler("序号", 0))
                    .registerWriteHandler(new DynamicFreezeHandler(1))
                    .registerWriteHandler(new MultiImageCellWriteHandler())
                    .build();

            WriteSheet sheet1 = EasyExcel.writerSheet("Sheet1").build();

            int pageNum = 1;
            int pageSize = 100;
            boolean hasMore;

            // 分页查询
            do {
                qry.setPageNo(pageNum);
                qry.setPageSize(pageSize);
                IPage<HouseMaterialDto> pageResult = customerHouseRepository.pageHouseMaterialDto(qry);
                if (CollectionUtils.isEmpty(pageResult.getRecords())) {
                    break;
                }

                // 添加商品图片
                addRowPic(pageResult.getRecords());

                excelWriter.write(pageResult.getRecords(), sheet1);
                hasMore = pageResult.getPages() > pageNum;
                pageNum++;
            } while (hasMore);
        } catch (IOException e) {
            log.error("导出文件异常", e);
            throw new ServiceException("导出文件异常");
        } finally {
            // 关闭excelWriter
            if (excelWriter != null) {
                excelWriter.finish();
            }
        }
    }

    private void addRowPic(List<HouseMaterialDto> dataList) {
        // 获取图片
        List<String> skuIds = dataList.stream()
                .map(item -> item.getSkuId().toString())
                .collect(Collectors.toList());
        Map<String, List<String>> skuPicUrlsMap = commonFileRepository.getSkuPicUrlsMap(skuIds, FileTypeEnum.COMMON_PIC.getType());

        dataList.forEach(item -> {
            if (skuPicUrlsMap.containsKey(String.valueOf(item.getSkuId()))) {
                item.setSkuPics(skuPicUrlsMap.get(String.valueOf(item.getSkuId())));
            }
        });
    }

    private void importRowCheck(List<HouseMaterialDto> dataList) {
        if (CollectionUtils.isEmpty(dataList)) {
            return;
        }

        // 校验表格重复的房产
        List<String> repeatHouseKeyList = new ArrayList<>();
        Map<String, List<HouseMaterialDto>> houseListMap = StreamUtil.listGroupBy(dataList, HouseMaterialDto::getHouseKey);
        houseListMap.forEach((houseKey, keyHouseList) -> {
            long count = keyHouseList.stream().map(HouseMaterialDto::getMobile).distinct().count();
            if (count > 1) {
                repeatHouseKeyList.add(houseKey);
            }
        });
        if (CollectionUtils.isNotEmpty(repeatHouseKeyList)) {
            throw new ServiceException(String.format("表格房产属于不同业主：%s", String.join(",", repeatHouseKeyList)));
        }

        // 校验已存在的房产
        List<String> buildingNums = new ArrayList<>();
        List<String> roomNums = new ArrayList<>();
        dataList.forEach(item -> {
            buildingNums.add(item.getBuildingNum());
            roomNums.add(item.getRoomNum());
        });

        List<CustomerHouseVo> customerHouseVos = customerHouseRepository.getBaseMapper().listBy(buildingNums, roomNums);
        Map<String, String> existHouseKeyMobileMap = StreamUtil.listToMap(customerHouseVos,
                CustomerHouseVo::getHouseKey, CustomerHouseVo::ofMobile);

        Set<String> existOtherHouseKeySet = new HashSet<>();
        houseListMap.forEach((houseKey, keyHouseList) -> {
            if (existHouseKeyMobileMap.containsKey(houseKey)
                    && !StringUtils.equals(existHouseKeyMobileMap.get(houseKey), keyHouseList.get(0).getMobile())) {
                existOtherHouseKeySet.add(houseKey);
            }
        });

        if (CollectionUtils.isNotEmpty(existOtherHouseKeySet)) {
            throw new ServiceException(String.format("房产已存在于其他业主：%s", String.join(",", existOtherHouseKeySet)));
        }
    }
}
